package com.lzx.hbh_system.service.Impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.lzx.hbh_system.bo.SysRole;
import com.lzx.hbh_system.bo.SysUser;
import com.lzx.hbh_system.bo.filter.UserRoleFilter;
import com.lzx.hbh_system.bo.filter.Userfilter;
import com.lzx.hbh_system.dto.UserDto;
import com.lzx.hbh_system.dto.UserQueryDto;
import com.lzx.hbh_system.dto.zffyxxgl.PayOrderInfoDto;
import com.lzx.hbh_system.mapper.SysRoleMapper;
import com.lzx.hbh_system.mapper.SysUserMapper;
import com.lzx.hbh_system.service.UserRoleService;
import com.lzx.hbh_system.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lzx.hbh_system.util.*;
import com.lzx.hbh_system.util.responseEntity.RespOutMsgHeader;
import com.lzx.hbh_system.util.responseEntity.ResponseView;
import lombok.extern.slf4j.Slf4j;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements UserService {
    @Autowired
    private SysUserMapper UserMapper;
    @Autowired
    private SysRoleMapper sysRoleMapper;
    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private ModelMapper modelMapper;
    @Autowired
    private UserRoleService userRoleService;

    @Override
    public SysUser getOneUser(SysUser sysUser) {

        return UserMapper.selectOneBymodel(sysUser);
    }

    @Override
    public ResponseView getAllUserList() {
        // 初始化
        ResponseView responseView = new ResponseView();
        List<SysUser> users = UserMapper.selectList(null);
        List<Map<String, Object>> mapList = new ArrayList<>();
        users.forEach(item -> {
            Map<String, Object> map = new HashMap<>();
            map.put("userCode", item.getUserCode());
            map.put("userName", item.getUserName().trim());
            mapList.add(map);
        });
        // 返回消息
        responseView.setRespOutMsgHeader(RespOutMsgHeader.success("查询成功"));
        responseView.setMain(mapList.toArray());
        return responseView;
    }

    /**
     * 获取用户信息
     *
     * @param userfilter
     * @return
     */
    @Override
    public ResponseView getOneUserByUsername(Userfilter userfilter) {
        ResponseView responseView = new ResponseView();
        if (userfilter == null) { // 简单校验请求体是否为空
            responseView.setRespOutMsgHeader(RespOutMsgHeader.error(false, "查询用户信息失败，空的请求载体，服务器未接收到请求数据，请重试！", 500));
            return responseView;
        }
        // 返回查询结果
        responseView.setRespOutMsgHeader(RespOutMsgHeader.success(1, true, "查询成功！"));
        responseView.setMain(UserMapper.searchByUserName(userfilter.getUserName().trim()));
        return responseView;
    }

    /**
     * 登陆验证，-成功后响应包含token字段
     *
     * @param userfilter
     * @return
     */
    @Override
    public ResponseView CheckLoingUserByNameAndPwd(Userfilter userfilter, HttpServletRequest request) {
        //初始化返回视图
        ResponseView responseView = new ResponseView();
        if (userfilter == null) {
            responseView.setRespOutMsgHeader(RespOutMsgHeader.error(false, "登陆失败，空的请求载体，服务器未接收到请求数据，请重试！", 500));
            return responseView;
        }
        //校验用户是否有设置的状态有效时间，若没有则默认给定1天 就是 24 小时
        if (userfilter.getStateTimeout() == null) {
            userfilter.setStateTimeout(1);//如果用户不设置保存状态，则默认设为1天
        }
        String token = null;
        //初始化->最近登陆时间
        String Logintime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date().getTime());
        //初始化->登陆IP地址
        String loginipaddress = RequestIPUtil.getIP(request);
        //初始化->有效状态时间
        Integer statustimeout = userfilter.getStateTimeout() * 24;
        //初始化注册登陆信息到redis的dto信息
        UserDto userDto = new UserDto();

/*
        如果redis存在该用户信息，说明处于登陆状态，则直接返回登陆成功
        如果不存在则表明未登录，或者登陆信息已经过期，则存入对应登陆信息salt值等
        详情这里--》
*/
        // 从redis中拿到当前已经登陆的用户信息
        UserDto loginuserDto = (UserDto) redisUtils.getObject(userfilter.getUserName().trim());
        // 设置->最近登陆时间
        userDto.setLogintime(Logintime);
        Map<String, Object> map = new HashMap<>();
        if (loginuserDto != null) {//redis存在
            //映射bo到dto
            System.out.println("Redis缓存该用户登陆信息：" + loginuserDto);
            modelMapper.map(loginuserDto, userDto);//先覆盖不需要变更的信息到新的userDto中，然后设置变更信息
            //设置->状态有效期
            userDto.setLifetime(statustimeout);//hours
            userDto.setLogintime(Logintime);
            userDto.setIpAddress(loginipaddress);//获取并设置登陆的ip地址
            userDto.setIslogin(true);//登陆
            String redisUserSalt = loginuserDto.getSalt();
            String redissecret = Md5Utils.MD5(redisUserSalt + loginuserDto.getUserName().trim());
            token = JWTUtil.sign(loginuserDto.getUserName().trim(), redissecret);
            // 新Token存入Redis缓存中
            // 存入Redis缓存
            setUserTokenToRedis(loginuserDto.getUserName().trim(),token);
            System.out.println("更新后Redis缓存用户登陆信息：" + userDto);
            System.out.println("用户：" + loginuserDto.getUserName().trim() + "的Redis缓存登陆状态信息为：" + loginuserDto.getIslogin());
            if (loginuserDto.getIslogin()) {//且为登陆状态下 ctrl+3 逻辑详情
                log.info(loginuserDto.toString());
            } else {//且为离线状态下 ctrl+4 逻辑详情
                //此时还是有效的，直接删除当前key，前面已经拿到旧key的值做保存了
                // 如果是修改密码后登出，那么redis中的salt还是旧的，那么生成的token也是之前的，但是是有效的
                // 再次传入到header中请求时，判断时还是使用了redis的旧salt值生成的secret值，所以还是匹配的
                //执行查询 -- 避免修改密码后还可以成功登陆的情况
                SysUser user = UserMapper.searchByUserNameAndUserPwd(userfilter.getUserName().trim(), Md5Utils.MD5(userfilter.getUserPwd()));
                if (user == null) {
                    responseView.setRespOutMsgHeader(RespOutMsgHeader.error(false, "账户或密码错误，请重试！", 500));
                    return responseView;
                }
                map.put("Userkey", user.getUserCode());// 转出用户编号用于查询个人信息
                System.out.println("redis存在，且离线状态下，再次登陆的情况");
                redisUtils.delete(userfilter.getUserName().trim());
                //重新写入redis
                redisUtils.setEx(userfilter.getUserName().trim(), userDto, statustimeout, TimeUnit.HOURS);
                //返回消息-登陆成功
            }
            responseView.setRespOutMsgHeader(RespOutMsgHeader.loginsuccess(1, true, "登陆成功！", token));
            responseView.setMain(userDto);
            return responseView;
        } else {//初始（首次）登陆，redis无该用户信息,直接数据库中查询，查询不为空生成新的token返回，说明登陆成功则最后同步信息到redis中
            //执行查询
            SysUser user = UserMapper.searchByUserNameAndUserPwd(userfilter.getUserName().trim(), Md5Utils.MD5(userfilter.getUserPwd().trim()));
            //返回消息设置
            if (ObjectUtils.isEmpty(user)) {
                responseView.setRespOutMsgHeader(RespOutMsgHeader.error(false, "用户账户或密码错误,请重试！", 500));
                return responseView;
            }
            //获得该用户的Salt值
            String UserSalt = user.getSalt();
            map.put("Userkey", user.getUserCode());// 转出用户编号用于查询个人信息
            //1  使用salt+MD5加密’用户密码‘后的密文再取MD5的值作为secret生成token
            // -- 问题 Shiro验证时 JWT校验还是需要solt+密码，密码获取还得从数据库中获取，回到老路上了。。。
            //2  使用salt+MD5加密’用户名‘后的密文再取MD5的值作为secret生成token
            // -- 问题 不需要查询数据库，若知道加密算法，很容易破解  暂时使用该方案
            String secret = Md5Utils.MD5(UserSalt + user.getUserName().trim());
            token = JWTUtil.sign(user.getUserName().trim(), secret);
            // 存入Redis缓存
            setUserTokenToRedis(user.getUserName().trim(),token);
            //映射bo到dto
            modelMapper.map(user, userDto);//这里bo类和dto有相同字段，若数据库中数据相同字段值为空，则拷贝后对应字段为空
            //设置->最新的登陆时间
            userDto.setLogintime(Logintime);
            //可能存在更换登陆设备，这里再次获取最新的请求IP地址
            userDto.setIpAddress(loginipaddress);//获取并设置登陆的ip地址
            //设置->登陆状态
            userDto.setIslogin(true);//登陆
            //设置->状态有效期
            userDto.setLifetime(statustimeout);//hours
            System.out.println("首次登陆情况");
            System.out.println(userDto);
            //以上功能尚未测试--待测试（redis基本存取操作）
            //记录：测试完成（redis基本存取操作）
            //时间：2022-1-26 14：00
            //redis过期删除策略---默认惰性和定期删除策略
            redisUtils.delete(userfilter.getUserName().trim());
            redisUtils.setEx(user.getUserName().trim(), userDto, statustimeout, TimeUnit.HOURS);
        }
        responseView.setRespOutMsgHeader(RespOutMsgHeader.loginsuccess(1, true, "登陆成功！", token));
        responseView.setMain(userDto);
        return responseView;
    }

    @Override
    public ResponseView loginout(Userfilter userfilter) {
        //初始化
        ResponseView responseView = new ResponseView();
        String token = userfilter.getToken();
        if (token == null) {
            responseView.setRespOutMsgHeader(RespOutMsgHeader.error(false, "token为空", 500));
            return responseView;
        }
        //获取当前登陆的用户名
        String currentloginUsername = JWTUtil.getUsername(token);
        //断开sse连接--》移除连接用户
        SseUtil.removeUser(currentloginUsername);
        //从redis中拿到该用户登陆信息
        UserDto OlduserDto = (UserDto) redisUtils.getObject(currentloginUsername);
        if (OlduserDto == null) {
            responseView.setRespOutMsgHeader(RespOutMsgHeader.success(0, true, "用户登出成功！"));
            return responseView;
        }
//        //映射为更新后用户登陆信息（拷贝）--暂时不需要用到
//        modelMapper.map(OlduserDto,userDto);
        //删除redis旧登陆信息
        redisUtils.delete(currentloginUsername);
        //设置离线状态
        UserDto NowuserDto = OlduserDto;
        NowuserDto.setIslogin(false);
        //设置离线有效期 重新设置key有效期：（离线有效期--换算成小时--） = （初始登陆有效期--换算成小时--)*20 %
        Integer livedate = Math.toIntExact(Math.round(OlduserDto.getLifetime() * 0.2));//四舍五入
        System.out.println("大概" + livedate + "小时");//输出
        long offlinetimout = livedate;
        NowuserDto.setLifetime((int) offlinetimout);
        redisUtils.delete(userfilter.getUserName().trim());
        redisUtils.setEx(NowuserDto.getUserName().trim(), NowuserDto, offlinetimout, TimeUnit.HOURS);
        SysUser sysUser = UserMapper.searchByUserName(currentloginUsername);
        //用户退出时，同步redis的最后salt、最近登陆时间、到数据库中
        modelMapper.map(NowuserDto, sysUser);//覆盖（拷贝）
        UserMapper.updateById(sysUser);
        //返回消息
        responseView.setRespOutMsgHeader(RespOutMsgHeader.success(0, true, "用户登出成功！"));
        return responseView;
    }

    @Override
    public ResponseView registerUser(Userfilter userfilter) {
        // 初始化
        ResponseView responseView = new ResponseView();
        // 请求体非空校验
        if (userfilter.getUserName().trim() == null) {
            responseView.setRespOutMsgHeader(RespOutMsgHeader.error(false, "空的请求载体，服务器未接收到请求数据，请重试！", 500));
        }
        // 密码账号非空校验
        if (userfilter.getUserName() == null && userfilter.getUserPwd() == null) {
            responseView.setRespOutMsgHeader(RespOutMsgHeader.error(false, "注册账号密码不能为空，且不能少于6字符，请重试！", 500));
        }
        // 密码账号长度校验
        if (userfilter.getUserName().trim().length() < 6 && userfilter.getUserPwd().trim().length() < 6) {
            responseView.setRespOutMsgHeader(RespOutMsgHeader.error(false, "注册账号密码不能少于6字符，请重试！", 500));
        }
        // 注册 校验是否用户重复
        if (!isNotUserNameRepeat(userfilter)) {
            responseView.setRespOutMsgHeader(RespOutMsgHeader.error(false, "该用户名已被注册，请尝试登陆或者重新注册其他账号！", 500));
            return responseView;
        }
        // 普通校验通过，则进入执行函数，进一步校验最后返回消息
        return executeRegisterFunction(userfilter);
    }

    @Override
    public ResponseView modifyUser(Userfilter userfilter) {
        // 初始化
        ResponseView responseView = new ResponseView();
        // 请求体非空校验
        if (userfilter.getUserPwd() == null && userfilter.getOriginPassword() == null) {
            responseView.setRespOutMsgHeader(RespOutMsgHeader.error(false, "空的请求载体，服务器未接收到请求数据，请重试！", 500));
            return responseView;
        }
        // 密码账号长度校验
        if (userfilter.getUserPwd().length() < 6) {
            responseView.setRespOutMsgHeader(RespOutMsgHeader.error(false, "修改的账号密码不能少于6字符，请重试！", 500));
            return responseView;
        }
        // 校验是否修改原密码是否一致
        if (!isOriginPassword(userfilter)) {
            // 不一致
            responseView.setRespOutMsgHeader(RespOutMsgHeader.error(false, "修改的账号原密码不一致，请重试", 500));
            return responseView;
        }
        // 调用执行函数
        return executeModifyFunction(userfilter);
    }

    @Override
    public ResponseView queryAllUserInfoPageFilter(Userfilter userfilter) {
        // 初始化返回视图
        // 校验参数
        ResponseView responseView = new ResponseView();
        // 逻辑实现
        // 初始化分页对象
        Page<UserQueryDto> userQueryDtoPage = new Page<>(userfilter.getPageIndex(), userfilter.getPageSize());
        // 查询
        IPage<UserQueryDto> userQueryDtoIPage = UserMapper.selectAllUserInfoPage(userQueryDtoPage, userfilter);
        // 处理结果集
        List<UserQueryDto> userQueryDtos = userQueryDtoIPage.getRecords();
        // 返回响应消息
        responseView.setRespOutMsgHeader(RespOutMsgHeader.success(userQueryDtos.size(), "查询成功！"));
        // 设置返回结果
        Map<String, Object> map = new HashMap<>();
        map.put("dataArray", userQueryDtos.toArray());
        map.put("currentPage", userQueryDtoIPage.getCurrent());
        map.put("totalsData", userQueryDtoIPage.getTotal());
        responseView.setMain(map);
        return responseView;
    }

    // 原密码校验
    public Boolean isOriginPassword(Userfilter userfilter) {
        // 获取原密码
        String OriginPassword_secret = Md5Utils.MD5(userfilter.getOriginPassword().trim());
        // 判断原密码是否一致
        String password_secret = UserMapper.searchByUserName(userfilter.getUserName().trim()).getUserPwd();
        if (!(password_secret.equals(OriginPassword_secret))) {
            // 不一致
            return false;
        }
        return true;
    }

    // 用户名校验函数
    public Boolean isNotUserNameRepeat(Userfilter userfilter) {
        // redis中不存在，或暂时不存在改用户名 减少直接数据库中查询提高校验效率
        if (redisUtils.getObject(userfilter.getUserName().trim()) != null) {
            return false;
        } else if (UserMapper.searchByUserName(userfilter.getUserName().trim()) != null) {
            // redis为空时在查询数据库该用户名是否为空
            return false;
        }
        //如果都为空，则通过校验，可以注册
        return true;
    }
    // 修改用户信息逻辑函数

    /**
     * 1 更新redis中存储的salt值
     * 2 设置redis登陆状态为登出 登出时会自动设置有效状态期
     * 3 前端显示修改完成，然后调用登出方法
     */
    public ResponseView executeModifyFunction(Userfilter userfilter) {
        // 初始化
        ResponseView responseView = new ResponseView();
        // 先获取当前redis中的登陆信息
        UserDto userDto = (UserDto) redisUtils.getObject(userfilter.getUserName().trim());
        // 更新redis登陆信息
        // 设置登陆状态为登出
        userDto.setIslogin(false);
        // 更新salt值 使其原token匹配失效
        String newSalt = SaltUtil.getSalt(5);
        userDto.setSalt(newSalt);
        // 保存新的登陆信息到redis中
        redisUtils.set(userfilter.getUserName().trim(), userDto);
        System.out.println(userDto);
        // 更新数据库对应密码信息、salt值
        SysUser sysUser = UserMapper.searchByUserName(userfilter.getUserName().trim());
        // MD5重新初次加密修改的新密码
        userfilter.setUserPwd(Md5Utils.MD5(userfilter.getUserPwd()));
        // 映射对象信息
        modelMapper.map(userfilter, sysUser);
        // 更新数据库的salt值
        sysUser.setSalt(newSalt);
        System.out.println(sysUser);
        // 更新数据库用户信息
        UserMapper.updateById(sysUser);
        // ...前端执行登出
        responseView.setRespOutMsgHeader(RespOutMsgHeader.success(1, true, "该用户信息修改成功，需要重新登陆！"));
        return responseView;
    }

    // 注册用户功能逻辑函数
    public ResponseView executeRegisterFunction(Userfilter userfilter) {
        // 初始化
        ResponseView responseView = new ResponseView();

        SysUser registerUser = new SysUser(); // 初始化一个User实体
        UserRoleFilter userRoleFilter = new UserRoleFilter(); // 初始化一个 用户角色入参实体
        String user_uniqueID = SnowFlakeUtil.getId();
        registerUser.setRegistTime(TurnDateToStringUtil.getTime()); // 注册时间
        registerUser.setSalt(SaltUtil.getSalt(5)); // 一般长度 5
        registerUser.setStatus("11"); // 设置用户状态标识
        registerUser.setId(user_uniqueID); // 数据唯一标识
        registerUser.setUserCode(userfilter.getUserName().trim() + "_" + user_uniqueID); // 设置主键 UserCode =  username + user_uniqueID
        registerUser.setUserName(userfilter.getUserName().trim());
        registerUser.setUserPwd(Md5Utils.MD5(userfilter.getUserPwd().trim())); // MD5进行初次加密
        registerUser.setValidFlag("1"); // 注册有效

        // 注册时的默认 用户角色关联 设置
        userRoleFilter.setUserCode(registerUser.getUserCode());// 注册默认全是普通用户，后续管理员在进行修改
        // 注册时要传入用户名用于校验
        userRoleFilter.setUserName(userfilter.getUserName().trim());
        // 注册时添加用户角色信息
        userRoleFilter.setResisting(true);
        // 查询数据库中
        SysRole sysRole = sysRoleMapper.searchByRoleName("普通用户");
        String rolecode = sysRole != null ? sysRole.getRoleCode() : "2";
        userRoleFilter.setRoleCode(rolecode);// 检索为'普通用户'的角色主键并设置
        ResponseView userRoleresponseView = userRoleService.insertUserRole(userRoleFilter);
        // 校验
        if (!userRoleresponseView.getRespOutMsgHeader().getRespStatus()) { // 说明有错误
            responseView.setRespOutMsgHeader(userRoleresponseView.getRespOutMsgHeader()); // 返回userrole对应实现类的错误信息
            return responseView;
        }
        // 若userrole服务没报错这说明可以插入对应角色信息，设置用户角色外键
        registerUser.setRoleCode(rolecode);
        UserMapper.insert(registerUser);
        responseView.setRespOutMsgHeader(RespOutMsgHeader.success(1, true, "用户注册账号成功，请尝试登陆或者授权后登陆！"));
        return responseView;
    }
    /**
     * 存放初始token
     * key : 用户名 + ‘token’ 字符串
     * @param userName
     * @param token
     */
    public void setUserTokenToRedis(String userName,String token){
        // key : 用户名 + ‘token’ 字符串
        long offlinetimout = 20;
        String tokenkey = userName+"token";
        redisUtils.setEx(tokenkey,token,offlinetimout, TimeUnit.MINUTES); // 初始token存放有效期 20分钟
    }
}